#pragma once

#include "pch.h"
#include "Assets/MeshGeometry.h"
#include "Graphics/FrameResource.h"
#include "Graphics/UploadBuffer.h"
#include "Utils/GameTimer.h"
#include "Utils/MathHelper.h"

extern const int GNumFrameResources;

// 存储绘制图形所需参数的轻量级结构体。它会随着不同的应用程序而有所差别
struct FRenderItem
{
    FRenderItem() = default;

    // 描述物体局部空间相对于世界空间的世界矩阵
    // 它定义了物体位于世界空间中的位置、朝向以及大小
    DirectX::XMFLOAT4X4 World = MathHelper::Identity4x4();

    // 用已更新标志(dirty flag)来表示物体的相关数据已发生改变，这意味着我们此时需要更新常量缓冲区。
    // 由于每个FrameResource中都有一个物体常量缓冲区，所以我们必须对每个FrameResource都进行更新。
    // 即，当我们修改物体数据的时候，应当按NumFramesDirty = GNumFrameResources进行设置，从而使
    // 每个帧资源都得到更新
    int NumFramesDirty = GNumFrameResources;

    // 该索引指向的GPU常量缓冲区对应于当前渲染项中的物体常量缓冲区
    UINT ObjCBIndex = -1;

    // 此渲染项参与绘制的集合体。注意，绘制一个集合体可能会用到多个渲染项
    FMeshGeometry* Geo = nullptr;

    // 图元拓扑
    D3D12_PRIMITIVE_TOPOLOGY PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;

    // DrawIndexedInstanced方法的参数
    UINT IndexCount = 0;
    UINT StartIndexLocation = 0;
    int BaseVertexLocation = 0;
};

class FEngine
{
public:
    static FEngine* Get()
    {
        static FEngine* Instance = new FEngine;
        return Instance;
    }
    
    void Run(HINSTANCE InInstance);

    bool Get4xMsaaState() const;
    void Set4xMsaaState(bool bInState);
    
    LRESULT MsgProc(HWND InWindow, UINT InMsg, WPARAM InWParam, LPARAM InLParam);

private:
    FEngine() = default;
    ~FEngine();
    FEngine(const FEngine& rhs) = delete;
    FEngine& operator=(const FEngine& rhs) = delete;
    FEngine& operator=(FEngine&& rhs) = delete;

    bool Initialize();
    void Tick();
    void Release();

    void CreateRtvAndDsvDescriptorHeaps();
    void OnResize();
    void Update(const GameTimer& InGameTimer);
    void Draw(const GameTimer& InGameTimer);

    void OnMouseDown(WPARAM btnState, int x, int y);
    void OnMouseUp(WPARAM btnState, int x, int y);
    void OnMouseMove(WPARAM btnState, int x, int y);

    float GetAspecRatio() const;
    bool InitMainWindow();
    bool InitDirect3D();
    void CreateCommandObjects();
    void CreateSwapChain();

    void FlushCommandQueue();

    ID3D12Resource* CurrentBackBuffer() const;
    D3D12_CPU_DESCRIPTOR_HANDLE CurrentBackBufferView() const;
    D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilView() const;

    void CalculateFrameStats();

private:
    HINSTANCE mAppInst = nullptr; // application instance handle
    HWND mMainWnd = nullptr; // main window handle
    bool bInitialized = false;
    bool bEnginePaused = false;
    bool bMinimized = false;
    bool bMaximized = false;
    bool bResizing = false;
    bool bFullScreenState = false;

    bool b4xMSAAState = false;
    UINT m4xMSAAQuality = 0;

    GameTimer mTimer;

    std::wstring mMainWndCaption = L"Kite Engine";
    DXGI_FORMAT mBackBufferFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
    DXGI_FORMAT mDepthStencilFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
    int mWindowWidth = 1080;
    int mWindowHeight = 720;

    Microsoft::WRL::ComPtr<IDXGIFactory4> mDxgiFactory;
    Microsoft::WRL::ComPtr<IDXGISwapChain> mSwapChain;
    Microsoft::WRL::ComPtr<ID3D12Device> mD3DDevice;

    Microsoft::WRL::ComPtr<ID3D12Fence> mFence;
    UINT64 mCurrentFence = 0;

    Microsoft::WRL::ComPtr<ID3D12CommandQueue> mCommandQueue;
    Microsoft::WRL::ComPtr<ID3D12CommandAllocator> mCommandListAlloc;
    Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList> mCommandList;

    static const int SwapChainBufferCount = 2;
    int mCurBackBuffer = 0;
    Microsoft::WRL::ComPtr<ID3D12Resource> mSwapChainBuffer[SwapChainBufferCount];
    Microsoft::WRL::ComPtr<ID3D12Resource> mDepthStencilBuffer;

    Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mRtvHeap;
    Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mDsvHeap;

    D3D12_VIEWPORT mScreenViewport;
    D3D12_RECT mScissorRect;

    UINT mRtvDescriptorSize = 0;
    UINT mDsvDescriptorSize = 0;
    UINT mCbvSrvUavDescriptorSize = 0;

private:
    void OnKeyboardInput(const GameTimer& InGameTimer);
    void UpdateCamera(const GameTimer& InGameTimer);
    void UpdateObjectCBs(const GameTimer& InGameTimer);
    void UpdateMainPassCB(const GameTimer& InGameTimer);
    
    void BuildDescriptorHeaps();
    void BuildConstantBufferViews();
    void BuildRootSignature();
    void BuildInputLayout();
    void BuildShapeGeometry();
    void BuildRenderItems();
    void BuildFrameResources();
    void BuildPSOs();

    void DrawRenderItems(ID3D12GraphicsCommandList* InCmdList, const std::vector<FRenderItem*>& InRenderItems);

    std::vector<std::unique_ptr<FFrameResource>> mFrameResources;
    FFrameResource* mCurFrameResource = nullptr;
    int mCurFrameResourceIndex = 0;

    Microsoft::WRL::ComPtr<ID3D12RootSignature> mRootSignature = nullptr;
    Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mCbvHeap = nullptr;

    std::unique_ptr<FUploadBuffer<ObjectConstants>> mObjectCB = nullptr;

    std::unordered_map<std::string, std::unique_ptr<FMeshGeometry>> mGeometries;
    std::unordered_map<std::string, Microsoft::WRL::ComPtr<ID3D12PipelineState>> mPSOs;

    std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout;

    // 存所有渲染项的向量
    std::vector<std::unique_ptr<FRenderItem>> mAllRenderItems;

    // 根据PSO来划分渲染项
    std::vector<FRenderItem*> mOpaqueRenderItems;
    std::vector<FRenderItem*> mTransparentRenderItems;

    PassConstants mMainPassCB;

    UINT mPassCbvOffset = 0;
    
    bool mIsWireframe = false;

    DirectX::XMFLOAT3 mEyePos = { 0.0f, 0.0f, 0.0f };
    DirectX::XMFLOAT4X4 mWorld = MathHelper::Identity4x4();
    DirectX::XMFLOAT4X4 mView = MathHelper::Identity4x4();
    DirectX::XMFLOAT4X4 mProj = MathHelper::Identity4x4();

    float mTheta = 1.5f*DirectX::XM_PI;
    float mPhi = 0.2f*DirectX::XM_PI;
    float mRadius = 15.0f;

    POINT mLastMousePos;
};
